package com.spring.boot.core.lock;

import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.StringRedisTemplate;

import com.spring.boot.core.utils.Utils;

public class RSyncLock implements RLock {
	private static final Logger logger = LoggerFactory.getLogger(RSyncLock.class);
	private static final int TETRY_TIME = 50;// 轮询时间,ms
	private static final int DEFAULT_EXPIRE_TIME = 15;// 默认超时时间,秒
	private static final int WAIT_TIME = 30;// 最大超时时间,秒
	private static final TimeUnit UNIT = TimeUnit.SECONDS;
	private final StringRedisTemplate redis;
	private final String lockKey;
	private final String lockValue = Utils.randomUID();

	public RSyncLock(String lockKey, StringRedisTemplate redis) {
		this.lockKey = lockKey;
		this.redis = redis;
	}

	@Override
	public void lock() {
		try {
			long begin = System.nanoTime();
			do {
				logger.debug("lock key: " + lockKey);
				boolean succ = redis.opsForValue().setIfAbsent(lockKey, lockValue);
				if (succ) {
					redis.expire(lockKey, DEFAULT_EXPIRE_TIME, UNIT);
					logger.debug("get lock, key: " + lockKey + " , expire in " + DEFAULT_EXPIRE_TIME + " seconds.");
					return;
				}
				Thread.sleep(TETRY_TIME);

				if (System.nanoTime() - begin >= UNIT.toNanos(WAIT_TIME)) {
					throw new RuntimeException("try get Lock[" + lockKey + "] time out!");
				}
			} while (true);
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}
	}

	@Override
	public boolean tryLock(long timeout) {
		try {
			long nano = System.nanoTime();
			do {
				logger.debug("try lock key: " + lockKey);
				boolean succ = redis.opsForValue().setIfAbsent(lockKey, lockValue);
				// 不存在锁
				if (succ) {
					redis.expire(lockKey, DEFAULT_EXPIRE_TIME, UNIT);
					logger.debug("get lock, key: " + lockKey + " , expire in " + DEFAULT_EXPIRE_TIME + " seconds.");
					return true;
				}
				if (timeout == 0) {
					break;
				}
				Thread.sleep(TETRY_TIME);
			} while ((System.nanoTime() - nano) < UNIT.toNanos(timeout));

			return false;
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}
		return false;
	}

	@Override
	public void uncLock() {
		redis.watch(lockKey);
		if (redis.opsForValue().get(lockKey).equals(lockValue)) {
			redis.multi();
			redis.delete(lockKey);
			redis.exec();
		}
		redis.unwatch();
	}
}
